package com.sdy.healthcode.biz.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sdy.common.model.BizException;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.DateUtil;
import com.sdy.common.utils.MapUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.healthcode.biz.constants.Constants;
import com.sdy.healthcode.biz.constants.ParamConstants;
import com.sdy.healthcode.biz.dto.DoctorDTO;
import com.sdy.healthcode.biz.dto.DoctorDetailDTO;
import com.sdy.healthcode.biz.dto.ExpertsAppointmentDTO;
import com.sdy.healthcode.biz.dto.GetAppointmentDTO;
import com.sdy.healthcode.biz.dto.ScheduleDTO;
import com.sdy.healthcode.biz.dto.cockpit.CockpitAppointmentStatistic;
import com.sdy.healthcode.biz.dto.cockpit.CockpitExpertsAppointment;
import com.sdy.healthcode.biz.dto.holiday.HolidayYearDTO;
import com.sdy.healthcode.biz.mapper.ExpertsAppointmentMapper;
import com.sdy.healthcode.biz.model.*;
import com.sdy.healthcode.biz.service.*;
import com.sdy.healthcode.biz.utils.IdCardUtil;
import com.sdy.mvc.service.impl.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.sql.Wrapper;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户-专家预约表 服务实现类
 * </p>
 *
 * @author zzq
 * @since 2020-03-04
 */
@Slf4j
@Service
public class ExpertsAppointmentServiceImpl extends BaseServiceImpl<ExpertsAppointment> implements ExpertsAppointmentService {
    @Resource
    private ExpertsAppointmentMapper expertsAppointmentMapper;
    @Autowired
    private ExpertsInformationService expertsInformationService;
    @Autowired
    private HsDepartmentService hsDepartmentService;
    @Autowired
    private UserService userService;
    @Autowired
    private HealthCodeService healthCodeService;
    @Autowired
    private SysParamService sysParamService;
    @Autowired
    private BlackListService blackListService;
    @Autowired
    private ExpertsScheduleDailyService expertsScheduleDailyService;
    @Autowired
    private UserPatientService userPatientService;
    @Autowired
    private HolidayService holidayService;

    @Override
    public List<ExpertsAppointment> listAppointmentByMap(Integer doctorId, Integer deptId, Date appointmentDateBegin, Date appointmentDateEnd) {
        return expertsAppointmentMapper.listAppointmentByMap(doctorId, deptId, DateUtil.formatDate(appointmentDateBegin), DateUtil.formatDate(appointmentDateEnd));
    }

    private long countDoctorAppointment(Integer expertsScheduleDailyId,
                                        List<ExpertsAppointment> data) {
        return data.stream()
                .filter(appointment -> appointment.getState().equals(1))
                .filter(appointment -> appointment.getExpertsScheduleDailyId().equals(expertsScheduleDailyId))
                .count();
    }

    /**
     * 将visitActual中的起始时间提取出来
     */
    private String getStartTimeOfVisitActualTime(String visitActualTime) {
        if (StringUtil.isNotBlank(visitActualTime)) {
            String[] ss = visitActualTime.split("~");
            if (ss.length == 2) {
                String time = ss[0];
                if (time.length() == 4) {
                    time = "0" + time;
                }
                if (time.length() == 8) {
                    return time;
                }
            }
        }
        return "00:00:00";
    }

    private List<ExpertsScheduleDaily> listExpertsScheduleForDay(Date dateBegin, Date dateEnd, Integer deptId, Integer doctorId) {
        return expertsScheduleDailyService.listExpertsScheduleForDay(dateBegin, dateEnd, deptId, doctorId);
    }

    @Override
    public List<ScheduleDTO> registerInfoOfDepartment(Integer deptId) throws BizException {
        HsDepartment department = hsDepartmentService.getById(deptId);
        Assert.isNull(department, "诊室信息不存在");
        int dayCount = 7;
        List<ScheduleDTO> result = new ArrayList<>();
        Date now = new Date();
        Date today = DateUtil.getDateBegin(now);
        Date dateStart = now.before(DateUtil.getDate(DateUtil.formatDate(now) + " 15:00:00", DateUtil.DATETIME_FORMAT))
                ? DateUtil.addDate(today, 1) : DateUtil.addDate(today, 2);
        List<ExpertsAppointment> appointmentData = listAppointmentByMap(null, deptId, dateStart, DateUtil.addDate(dateStart, 6));
        // 获取7天的部门排班信息
        List<ExpertsScheduleDaily> expertsScheduleDailyAllList = listExpertsScheduleForDay(dateStart, DateUtil.addDate(dateStart, 6), deptId, null);
        // 获取医生信息
        Map<Integer, ExpertsInformation> expertsInformationMap = Collections.emptyMap();
        Map<Integer, ExpertsInformation> expertsInformationRepMap = Collections.emptyMap();
        if (!expertsScheduleDailyAllList.isEmpty()) {
            List<ExpertsInformation> expertsInformations = new ArrayList<>(expertsInformationService.listByIds(expertsScheduleDailyAllList.stream()
                    .map(ExpertsScheduleDaily::getDoctorId).collect(Collectors.toSet())));
            expertsInformationMap = MapUtil.collectionToMap(expertsInformations, ExpertsInformation::getId);
            Set<Integer> repDoctorIds = expertsInformations.stream().map(ExpertsInformation::getReplaceDoctor).filter(Objects::nonNull).collect(Collectors.toSet());
            if (!repDoctorIds.isEmpty()) {
                List<ExpertsInformation> expertsInformationsRep = new ArrayList<>(expertsInformationService.listByIds(repDoctorIds));
                expertsInformationRepMap = MapUtil.collectionToMap(expertsInformationsRep, ExpertsInformation::getId);
            }
        }
        // 获取替诊医生信息
        for (int i = 0; i < dayCount; i++) {
            boolean canRegister = false;
            Date day = DateUtil.addDate(dateStart, i);
            // 获取这天的医生排班
            List<ExpertsScheduleDaily> expertsScheduleDailyList = expertsScheduleDailyAllList.stream()
                    .filter(item -> item.getScheduleDate().equals(day))
                    .collect(Collectors.toList());
            for (ExpertsScheduleDaily doctorSchedule : expertsScheduleDailyList) {
                // 获取排班接客总人数
                int limit = doctorSchedule.getVisitNum();
                // 获取医生信息
                ExpertsInformation experts = expertsInformationMap.get(doctorSchedule.getDoctorId());
                if (experts == null) {
                    log.error("找不到医生ID[{}]信息，排班ID[{}]", doctorSchedule.getDoctorId(), doctorSchedule.getId());
                    continue;
                }
                // 替诊医生
                Integer replaceDoctorId = experts.getReplaceDoctor();
                if (experts.getSuspendService().equals(4)) {
                    // 临时停诊，查询替诊医生
                    if (experts.getReplaceDoctor() == null) {
                        // 没有替诊医生，则无号
                        replaceDoctorId = null;
                    } else {
                        ExpertsInformation replaceDoctor = expertsInformationRepMap.get(experts.getReplaceDoctor());
                        // 替诊医生也停诊
                        if (replaceDoctor == null || !replaceDoctor.getSuspendService().equals(1)) {
                            replaceDoctorId = null;
                        }
                    }
                }
                // 停诊 号数为0
                if (experts.getSuspendService().equals(2) || experts.getSuspendService().equals(3)) {
                    limit = 0;
                } else if (experts.getSuspendService().equals(4) && replaceDoctorId == null) {
                    limit = 0;
                }
                // 获取已预约人数
                int appointmentCount = (int) countDoctorAppointment(doctorSchedule.getId(), appointmentData);
                if (limit > appointmentCount) {
                    canRegister = true;
                    break;
                }
            }
            String dateStr = DateUtil.formatDate(day);
            result.add(new ScheduleDTO()
                    .setDept(department.getName())
                    .setCanRegister(canRegister)
                    .setDate(dateStr)
                    .setMonth(DateUtil.dateToMonth(day) + "")
                    .setWeek(Constants.WEEK_Z_STR[DateUtil.dateToWeek(day) % 7])
                    .setDay(String.format("%02d", DateUtil.dateToDay(day))));
        }
        return result;
    }

    @Override
    public List<DoctorDTO> registerInfoOfDepartmentAndDay(Integer deptId, String date) {
        Date day = DateUtil.getDate(date, DateUtil.DATE_FORMAT);
        // 获取这天的医生排班
        List<ExpertsScheduleDaily> expertsScheduleDailyList = listExpertsScheduleForDay(day, day, deptId, null);
        if (expertsScheduleDailyList.isEmpty()) {
            return Collections.emptyList();
        }
        Collection<ExpertsInformation> expertsInformations = expertsInformationService.listByIds(
                expertsScheduleDailyList.stream().map(ExpertsScheduleDaily::getDoctorId).collect(Collectors.toSet()));
        Map<Integer, DoctorDTO> doctorMap = new HashMap<>();
        // 替诊医生信息
        Map<Integer, ExpertsInformation> expertsInformationRepMap = Collections.emptyMap();
        Set<Integer> repDoctorIds = expertsInformations.stream().map(ExpertsInformation::getReplaceDoctor).filter(Objects::nonNull).collect(Collectors.toSet());
        if (!repDoctorIds.isEmpty()) {
            List<ExpertsInformation> expertsInformationsRep = new ArrayList<>(expertsInformationService.listByIds(repDoctorIds));
            expertsInformationRepMap = MapUtil.collectionToMap(expertsInformationsRep, ExpertsInformation::getId);
        }
        expertsInformations.forEach(expertsInformation -> {
            if (!expertsInformation.getSuspendService().equals(3)) {
                doctorMap.put(expertsInformation.getId(),
                        new DoctorDTO()
                                .setAvatar(expertsInformation.getPhotoPath())
                                .setReplaceDoctorId(expertsInformation.getReplaceDoctor())
                                .setSuspendService(expertsInformation.getSuspendService())
                                .setScheduleState(2)
                                .setIntroduction(expertsInformation.getIntroduction())
                                .setName(expertsInformation.getName())
                                .setDoctorId(expertsInformation.getId()));
            }
        });
        List<ExpertsAppointment> appointmentData = listAppointmentByMap(null, deptId, day, day);
        for (ExpertsScheduleDaily doctorSchedule : expertsScheduleDailyList) {
            DoctorDTO doctorDTO = doctorMap.get(doctorSchedule.getDoctorId());
            if (doctorDTO == null) {
                continue;
            }
            // 获取排班接客总人数
            int limit = doctorSchedule.getVisitNum();
            // 临时停诊，查询替诊医生
            if (doctorDTO.getSuspendService().equals(4)) {
                if (doctorDTO.getReplaceDoctorId() == null) {
                    // 没有替诊医生
                    doctorDTO.setReplaceDoctorId(null);
                } else {
                    ExpertsInformation replaceDoctor = expertsInformationRepMap.get(doctorDTO.getReplaceDoctorId());
                    // 替诊医生也停诊
                    if (replaceDoctor == null || !replaceDoctor.getSuspendService().equals(1)) {
                        doctorDTO.setReplaceDoctorId(null);
                    }
                }
            }
            // 停诊 号数为0
            if (doctorDTO.getSuspendService().equals(2) || doctorDTO.getSuspendService().equals(3)) {
                limit = 0;
            } else if (doctorDTO.getSuspendService().equals(4) && doctorDTO.getReplaceDoctorId() == null) {
                limit = 0;
            }
            // 获取已预约人数
            int appointmentCount = (int) countDoctorAppointment(doctorSchedule.getId(), appointmentData);
            if (limit > appointmentCount) {
                doctorDTO.setScheduleState(1);
            }
        }
        List<DoctorDTO> result = new ArrayList<>(doctorMap.values());
        List<Integer> collect = result.stream().map(DoctorDTO::getDoctorId).collect(Collectors.toList());
        if (collect.size() > 0) {
            List<ExpertsInformation> list = expertsInformationService.list(Wrappers.<ExpertsInformation>lambdaQuery().inOrThrow(ExpertsInformation::getId, collect));
            if (list != null) {
                for (int i = 0; i < result.size(); i++) {
                    for (int j = 0; j < list.size(); j++) {
                        if (result.get(i).getDoctorId().equals(list.get(j).getId())) {
                            result.get(i).setPrice(list.get(j).getVisitPrice());
                        }
                    }
                }
            }
        }
        result.sort((o1, o2) -> {
            if (o1.getScheduleState().equals(o2.getScheduleState())) {
                return o1.getDoctorId() - o2.getDoctorId();
            } else {
                return o1.getScheduleState() - o2.getScheduleState();
            }
        });
        return result;
    }

    @Override
    public DoctorDetailDTO expertScheduleDetail(Integer doctorId, String date) {
        ExpertsInformation expert = expertsInformationService.getById(doctorId);
        if (expert == null) {
            return null;
        }
        BigDecimal price = null;
        if (expert != null && expert.getVisitPrice() != null) {
            price = expert.getVisitPrice();
        }
        HsDepartment department = hsDepartmentService.getById(expert.getDepartmentId());
        if (department == null) {
            return null;
        }
        DoctorDetailDTO doctorDetailDTO = new DoctorDetailDTO();
        doctorDetailDTO
                .setDoctorId(expert.getId())
                .setDept(department.getName())
                .setWeekStr(Constants.WEEK_Z_STR[DateUtil.dateToWeek(DateUtil.getDate(date, DateUtil.DATE_FORMAT))])
                .setDate(date)
                .setIntroduction(expert.getIntroduction())
                .setName(expert.getName())
                .setSuspendService(expert.getSuspendService())
                .setScheduleState(2)
                .setPrice(price)
                .setAvatar(expert.getPhotoPath());
        if (expert.getSuspendService().equals(4)) {
            // 临时停诊，查询替诊医生
            if (expert.getReplaceDoctor() != null) {
                ExpertsInformation replaceDoctor = expertsInformationService.getById(expert.getReplaceDoctor());

                // 替诊医生
                if (replaceDoctor != null && replaceDoctor.getSuspendService().equals(1)) {
                    doctorDetailDTO.setDoctorRep(new DoctorDetailDTO.DoctorRep()
                            .setDoctorId(expert.getReplaceDoctor())
                            .setAvatar(replaceDoctor.getPhotoPath())
                            .setDept(department.getName())
                            .setIntroduction(replaceDoctor.getIntroduction())
                            .setName(replaceDoctor.getName())
                            .setRepPrice(replaceDoctor.getVisitPrice()));
                }
            }
        }
        List<DoctorDetailDTO.ScheduleDay> scheduleDays = new ArrayList<>();
        Date day = DateUtil.getDate(date, DateUtil.DATE_FORMAT);
        List<ExpertsAppointment> appointmentData = listAppointmentByMap(doctorId, expert.getDepartmentId(), day, day);
        // 获取这天的医生排班
        List<ExpertsScheduleDaily> expertsScheduleDailyList = listExpertsScheduleForDay(day, day, null, doctorId);
        if (!expertsScheduleDailyList.isEmpty()) {
            for (ExpertsScheduleDaily doctorSchedule : expertsScheduleDailyList) {
                // 获取排班接客总人数
                int limit = doctorSchedule.getVisitNum();
                // 停诊 号数为0
                if (expert.getSuspendService().equals(2) || expert.getSuspendService().equals(3)) {
                    limit = 0;
                } else if (expert.getSuspendService().equals(4) && doctorDetailDTO.getDoctorRep() == null) {
                    limit = 0;
                }
                // 获取已预约人数
                int appointmentCount = (int) countDoctorAppointment(doctorSchedule.getId(), appointmentData);
                if (limit > appointmentCount) {
                    doctorDetailDTO.setScheduleState(1);
                }
                scheduleDays.add(new DoctorDetailDTO.ScheduleDay()
                        .setWeekDay(doctorSchedule.getVisitDay().substring(0, 2))
                        .setDayStr(doctorSchedule.getVisitDay().substring(2))
                        .setRest(Math.max(0, limit - appointmentCount))
                        .setVisitTime(doctorSchedule.getVisitTime())
                        .setExpertsScheduleDailyId(doctorSchedule.getId()));
            }
        }
        scheduleDays.sort(Comparator.comparing(o -> Integer.parseInt(o.getVisitTime().split(":")[0])));
        doctorDetailDTO.setScheduleDays(scheduleDays);
        return doctorDetailDTO;
    }

    /**
     * 获取用户预约的排班信息
     *
     * @param userId 用户ID
     * @param date   预约日期
     */
    private List<ExpertsScheduleDaily> listUserAppointmentSchedule(Integer userId, Date date) {
        List<ExpertsAppointment> userAppointments = lambdaQuery()
                .eq(ExpertsAppointment::getState, 1)
                .eq(ExpertsAppointment::getAppointmentDate, date)
                .eq(ExpertsAppointment::getUserId, userId).list();
        if (userAppointments.isEmpty()) {
            return Collections.emptyList();
        }
        return new ArrayList<>(expertsScheduleDailyService.listByIds(
                userAppointments.stream().map(ExpertsAppointment::getExpertsScheduleDailyId).collect(Collectors.toSet())));

    }

    private List<Integer> listScheduleDailyDeptId(List<ExpertsScheduleDaily> scheduleDailyList) {
        Set<Integer> expertsIdSet = scheduleDailyList.stream().map(ExpertsScheduleDaily::getDoctorId).collect(Collectors.toSet());
        if (!expertsIdSet.isEmpty()) {
            Collection<ExpertsInformation> experts = expertsInformationService.listByIds(expertsIdSet);
            return experts.stream().map(ExpertsInformation::getDepartmentId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    @Override
    public Integer tryExpertAppointment(Integer userId, Integer expertsScheduleDailyId, Integer userPatientId) throws BizException {
        // 黑名单检查
        BlackList blackList = blackListService.userInBlackList(userId);
        if (blackList != null) {
            Assert.isTrue(blackList.getOutBannedTime().after(new Date()), blackList.getReason());
        }
        // 就诊人信息
        UserPatient userPatient = null;
        if (userPatientId != null) {
            userPatient = userPatientService.getById(userPatientId);
            Assert.isNull(userPatient, "就诊人信息不存在");
        }
        // 获取排班接客信息
        ExpertsScheduleDaily expertsScheduleDaily = expertsScheduleDailyService.getById(expertsScheduleDailyId);
        Assert.isNull(expertsScheduleDaily, "排班信息不存在。");
        // 专家状态检查
        ExpertsInformation expertsInformation = expertsInformationService.getById(expertsScheduleDaily.getDoctorId());
        if (expertsInformation.getSuspendService().equals(4) && expertsInformation.getReplaceDoctor() != null) {
            ExpertsInformation expertsInformationRep = expertsInformationService.getById(expertsInformation.getReplaceDoctor());
            Assert.isTrue(expertsInformationRep == null || !expertsInformationRep.getSuspendService().equals(1), "医生已停诊。");
        } else {
            Assert.isTrue(!expertsInformation.getSuspendService().equals(1), "医生已停诊。");
        }
        // 预约时间检查
        Date now = new Date();
        Date appointmentBeginDate = now.before(DateUtil.getDate(DateUtil.formatDate(now) + " 15:00:00", DateUtil.DATETIME_FORMAT))
                ? DateUtil.addDate(DateUtil.getDateBegin(now), 1) : DateUtil.addDate(DateUtil.getDateBegin(now), 2);
        Date appointmentEndDate = DateUtil.addDate(appointmentBeginDate, 6);
        Assert.isTrue(expertsScheduleDaily.getScheduleDate().before(appointmentBeginDate)
                || expertsScheduleDaily.getScheduleDate().after(appointmentEndDate), "预约时间不在排班范围。");
        // 获取排班接客总人数
        int limit = expertsScheduleDaily.getVisitNum();
        // 同一科室重复预约检查
        if (userPatientId == null) {
            // 自己给自己预约
            long cnt = this.listExpertsAppointmentInfo(userId, 1, expertsScheduleDaily.getScheduleDate(), expertsInformation.getDepartmentId())
                    .stream().filter(ExpertsAppointmentDTO::getSelf).count();
            Assert.isTrue(cnt > 0, "您已预约了该科室“" + DateUtil.formatDate(expertsScheduleDaily.getScheduleDate()) + "”的号，同一天同一科室不能重复预约！");
        } else {
            // 自己给他人预约
            List<UserPatient> userPatients = userPatientService.lambdaQuery().eq(UserPatient::getCardId, userPatient.getCardId()).list();
            if (!userPatients.isEmpty()) {
                int cnt = expertsAppointmentMapper.countPatientAppointmentInfo(userPatient.getCardId(), 1, DateUtil.formatDate(expertsScheduleDaily.getScheduleDate()), expertsInformation.getDepartmentId());
                if (cnt <= 0) {
                    List<User> others = userService.lambdaQuery().eq(User::getCardId, userPatient.getCardId()).list();
                    if (!others.isEmpty()) {
                        for (User other : others) {
                            cnt += this.listExpertsAppointmentInfo(other.getId(), 1, expertsScheduleDaily.getScheduleDate(), expertsInformation.getDepartmentId())
                                    .stream().filter(ExpertsAppointmentDTO::getSelf).count();
                            if (cnt > 0) {
                                break;
                            }
                        }
                    }
                }
                Assert.isTrue(cnt > 0, "该就诊人已预约了该科室“" + DateUtil.formatDate(expertsScheduleDaily.getScheduleDate()) + "”的号，同一天同一科室不能重复预约！");
            }
        }
        // 获取已预约人数
        int appointmentCount = lambdaQuery()
                .eq(ExpertsAppointment::getState, 1)
                .eq(ExpertsAppointment::getExpertsScheduleDailyId, expertsScheduleDaily.getId()).count();
        log.info("用户[{}]尝试预约[{}]，总人数[{}]，已预约人数[{}]", userId, expertsScheduleDaily.getDoctorId(), limit, appointmentCount);
        if (limit > appointmentCount) {
            ExpertsAppointment appointment = new ExpertsAppointment()
                    .setCreateTime(new Date())
                    .setUpdateTime(new Date())
                    .setUserId(userId)
                    .setState(1)
                    .setExpertsScheduleDailyId(expertsScheduleDailyId)
                    .setUserPatientId(userPatientId)
                    .setAppointmentDate(expertsScheduleDaily.getScheduleDate());
            save(appointment);
            healthCodeService.updateUserToPatient(userId);
            return appointment.getId();
        } else {
            return null;
        }
    }

    @Override
    public ExpertsAppointmentDTO getExpertInfo(Integer userId) throws BizException {
        ExpertsAppointment appointment = lambdaQuery()
                .eq(ExpertsAppointment::getUserId, userId)
                .eq(ExpertsAppointment::getState, 1)
                .eq(ExpertsAppointment::getAppointmentDate, DateUtil.getDateBegin(new Date()))
                .orderByDesc(ExpertsAppointment::getId)
                .last("limit 1")
                .one();
        if (appointment == null) {
            return null;
        }
        ExpertsScheduleDaily scheduleDaily = expertsScheduleDailyService.getById(appointment.getExpertsScheduleDailyId());
        Assert.isNull(scheduleDaily, "排班数据不存在。");
        ExpertsInformation expert = expertsInformationService.getById(scheduleDaily.getDoctorId());
        Assert.isNull(expert, "医生数据不存在。");
        HsDepartment department = hsDepartmentService.getById(expert.getDepartmentId());
        Assert.isNull(department, "诊室信息不存在");
        return new ExpertsAppointmentDTO()
                .setId(appointment.getId())
                .setDept(department.getName())
                .setAppointmentDate(appointment.getAppointmentDate())
                .setDayStr(scheduleDaily.getVisitDay().substring(2))
                .setDoctorName(expert.getName())
                .setVisitTime(scheduleDaily.getVisitTime());
    }

    @Override
    public List<ExpertsAppointmentDTO> listExpertsAppointmentInfo(Integer userId, Integer state, Date date, Integer deptId) {
        return expertsAppointmentMapper.listExpertsAppointmentInfo(userId, state, date == null ? null : DateUtil.formatDate(DateUtil.getDateBegin(date)), deptId);
    }

    @Override
    public IPage<ExpertsAppointmentDTO> pageExpertsAppointmentInfo(Page<ExpertsAppointmentDTO> page, Integer userId, Integer state, Date date) {
        return expertsAppointmentMapper.pageExpertsAppointmentInfo(page, userId, state, date == null ? null : DateUtil.formatDate(DateUtil.getDateBegin(date)));
    }

    @Override
    public void cancelApply(Integer userId, Integer expertsAppointmentId) throws BizException {
        Assert.isNull(expertsAppointmentId, "参数错误！");
        ExpertsAppointment appointment = getById(expertsAppointmentId);
        Assert.isNull(appointment, "预约信息不存在。");
        User user = userService.getById(userId);
        if (appointment.getUserPatientId() == null) {
            Assert.isTrue(!appointment.getUserId().equals(userId), "用户权限不足。");
        } else {
            UserPatient userPatient = userPatientService.getById(appointment.getUserPatientId());
            Assert.isTrue(!appointment.getUserId().equals(userId) &&
                    (userPatient == null || !user.getCardId().equalsIgnoreCase(userPatient.getCardId())), "用户权限不足。");
        }
        boolean r = lambdaUpdate()
                .eq(ExpertsAppointment::getId, expertsAppointmentId)
                .set(ExpertsAppointment::getState, 2)
                .set(ExpertsAppointment::getUpdateTime, new Date())
                .update();
        if (r) {
            // 取消成功后，黑名单增加计数
            int cnt = countCancelApply(userId);
            int maxCntLimit = Integer.valueOf(sysParamService.getOrDefault(ParamConstants.EXPERTS_APPOINTMENT_BLACK_LIST_COUNT, "3"));
            if (cnt >= maxCntLimit) {
                blackListService.banUser(
                        userId,
                        Integer.valueOf(sysParamService.getOrDefault(ParamConstants.EXPERTS_APPOINTMENT_BLACK_LIST_DAYS, "30")),
                        String.format("您近30天主动取消预约已经达到%s次，无法在网上预约挂号。", cnt));
            }
        } else {
            throw new BizException("取消预约失败，请稍后重试。");
        }
    }

    /**
     * 用户在一段时间内的取消预约次数
     */
    private int getUserCancelCountInDateRange(Integer userId, Date dateBegin, Date dateEnd) {
        return lambdaQuery()
                .eq(ExpertsAppointment::getUserId, userId)
                .eq(ExpertsAppointment::getState, 2)
                .between(ExpertsAppointment::getCreateTime, dateBegin, dateEnd)
                .count();
    }

    @Override
    public Integer countHospitalIn(String dateStr) {
        return expertsAppointmentMapper.countHospitalIn(dateStr);
    }

    @Override
    public Integer countCancelApply(Integer userId) {
        Date now = new Date();
        return getUserCancelCountInDateRange(userId, DateUtil.getMonthBegin(now), DateUtil.getMonthEnd(now));
    }

    @Override
    public void updateSettings(Integer expertsAppointmentBlackListCount,
                               Integer expertsAppointmentBlackListDays,
                               Boolean scheduleForHoliday,
                               HolidayYearDTO holidayConfig) {
        if (expertsAppointmentBlackListCount != null) {
            sysParamService.updateParam(ParamConstants.EXPERTS_APPOINTMENT_BLACK_LIST_COUNT, expertsAppointmentBlackListCount + "");
        }
        if (expertsAppointmentBlackListDays != null) {
            sysParamService.updateParam(ParamConstants.EXPERTS_APPOINTMENT_BLACK_LIST_DAYS, expertsAppointmentBlackListDays + "");
        }
        if (scheduleForHoliday != null) {
            sysParamService.updateParam(ParamConstants.SCHEDULE_FOR_HOLIDAY, scheduleForHoliday.toString());
        }
        if (holidayConfig != null) {
            holidayService.setHolidays(holidayConfig);
        }
    }


    @Override
    public IPage<GetAppointmentDTO> getAppointmentPageList(Integer state, Integer docState, String name, String startTime, String endTime, Page page) throws BizException {
        List<GetAppointmentDTO> appointmentPageList = expertsAppointmentMapper
                .getAppointmentPageList(state, docState, name, startTime, endTime, (page.getCurrent() - 1) * page.getSize(), page.getSize());
        Integer count = expertsAppointmentMapper
                .getAppointmentPageCount(state, docState, name, startTime, endTime);
        IPage<GetAppointmentDTO> iPage = new Page().setCurrent(page.getCurrent()).setTotal(count).setSize(page.getSize());
        if (appointmentPageList.isEmpty()) {
            return iPage;
        }
        appointmentPageList.forEach(getAppointmentDTO -> {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            Date scheduleDate = getAppointmentDTO.getScheduleDate();
            if (scheduleDate != null) {
                String date = format.format(scheduleDate);
                getAppointmentDTO.setVisitTime(date + " " + getAppointmentDTO.getVisitTime());
            }
            String cardId = getAppointmentDTO.getIdCard();
            String checkIdCard = com.sdy.common.utils.IdCardUtil.checkIdCard(cardId == null ? "" : cardId);
            if ("true".equals(checkIdCard)) {
                // 通过身份证号码判断该患者性别以及年龄
                try {
                    String sex = IdCardUtil.getSexByIdCard(cardId);
                    Integer age = IdCardUtil.getAgeByDateString(cardId);
                    getAppointmentDTO.setSex(sex).setAge(age);
                } catch (BizException | ParseException e) {
                    log.error("根据身份证计算患者年龄和性别时出现错误", e);
                }
            }
        });
        return iPage.setRecords(appointmentPageList);
    }

    @Override
    public List<GetAppointmentDTO> getAppointmentList(Integer state, Integer docState, String name, String startTime, String endTime) throws BizException {
        List<GetAppointmentDTO> appointmentPageList = expertsAppointmentMapper
                .getAppointmentPageList(state, docState, name, startTime, endTime, null, null);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        appointmentPageList.forEach(getAppointmentDTO -> {
            String cardId = getAppointmentDTO.getIdCard();
            String checkIdCard = com.sdy.common.utils.IdCardUtil.checkIdCard(cardId == null ? "" : cardId);
            if (getAppointmentDTO.getScheduleDate() != null) {
                getAppointmentDTO.setScheduleDateStr(format.format(getAppointmentDTO.getScheduleDate()));
            }
            if (getAppointmentDTO.getSubDocName() != null) {
                // 如果替班医生数据不为空则替换为替班医生数据
                getAppointmentDTO.setDocName(getAppointmentDTO.getSubDocName());
            }
            if ("true".equals(checkIdCard)) {
                // 通过身份证号码判断该患者性别以及年龄
                try {
                    String sex = IdCardUtil.getSexByIdCard(cardId);
                    Integer age = IdCardUtil.getAgeByDateString(cardId);
                    getAppointmentDTO.setSex(sex).setAge(age);
                } catch (BizException | ParseException e) {
                    log.error("根据身份证计算患者年龄和性别时出现错误", e);
                }
            }
        });
//        String[] stateStr = {"", "接诊", "长期停诊", "不接诊", "接诊 临时停诊"};
//        for (GetAppointmentDTO dto : appointmentPageList) {
//            if (dto.getState() != null) {
//                dto.setStates(dto.getState() == 1 ? "有效" : dto.getState() == 2 ? "已取消" : "已过期");
//            }
//            if (dto.getDocState() != null) {
//                dto.setDocStates(stateStr[dto.getDocState()]);
//            }
//            if (dto.getSubDocState() != null) {
//                // 如果替班医生状态不为空，则将替班医生状态放入医生状态
//                dto.setDocStates(stateStr[dto.getSubDocState()]);
//            }
//        }
        return appointmentPageList;
    }

    @Override
    public List<CockpitAppointmentStatistic> deptAppointmentMap() {
        return expertsAppointmentMapper.deptAppointmentMap();
    }

    @Override
    public IPage<CockpitExpertsAppointment> pageExpertsAppointmentInfo(Page<CockpitExpertsAppointment> page, Integer state, Date date) {
        IPage<ExpertsAppointment> appointments = lambdaQuery()
                .eq(ExpertsAppointment::getState, state)
                .eq(ExpertsAppointment::getAppointmentDate, DateUtil.getDateBegin(date))
                .orderByDesc(ExpertsAppointment::getId)
                .page(new Page<>(page.getCurrent(), page.getSize()));
        return new Page<CockpitExpertsAppointment>(page.getCurrent(), page.getSize(), appointments.getTotal())
                .setRecords(formatCockpitExpertsAppointmentList(appointments.getRecords()));
    }


    private List<CockpitExpertsAppointment> formatCockpitExpertsAppointmentList(List<ExpertsAppointment> appointments) {
        List<CockpitExpertsAppointment> result = new ArrayList<>();
        if (appointments.isEmpty()) {
            return Collections.emptyList();
        }
        Set<Integer> userIdSet = appointments.stream().map(ExpertsAppointment::getUserId).collect(Collectors.toSet());
        Map<Integer, User> userMap = Collections.emptyMap();
        if (!userIdSet.isEmpty()) {
            Collection<User> users = userService.listByIds(userIdSet);
            userMap = MapUtil.collectionToMap(users, User::getId);
        }
        Collection<ExpertsScheduleDaily> expertsScheduleDailys = expertsScheduleDailyService.listByIds(
                appointments.stream().map(ExpertsAppointment::getExpertsScheduleDailyId).collect(Collectors.toSet()));
        if (expertsScheduleDailys.isEmpty()) {
            return Collections.emptyList();
        }
        Collection<ExpertsInformation> experts = expertsInformationService.listByIds(
                expertsScheduleDailys.stream().map(ExpertsScheduleDaily::getDoctorId).collect(Collectors.toSet()));
        Map<Integer, ExpertsInformation> expertsInformationMap = MapUtil.collectionToMap(experts, ExpertsInformation::getId);
        Map<Integer, ExpertsScheduleDaily> expertsScheduleDailyMap = MapUtil.collectionToMap(expertsScheduleDailys, ExpertsScheduleDaily::getId);
        Map<Integer, HsDepartment> departmentMap = Collections.emptyMap();
        Set<Integer> deptidset = experts.stream().map(ExpertsInformation::getDepartmentId).filter(Objects::nonNull).collect(Collectors.toSet());
        if (!deptidset.isEmpty()) {
            Collection<HsDepartment> hsDepartments = hsDepartmentService.listByIds(deptidset);
            departmentMap = MapUtil.collectionToMap(hsDepartments, HsDepartment::getId);
        }
        for (ExpertsAppointment appointment : appointments) {
            ExpertsScheduleDaily expertsScheduleDaily = expertsScheduleDailyMap.get(appointment.getExpertsScheduleDailyId());
            if (expertsScheduleDaily == null) {
                continue;
            }
            ExpertsInformation expert = expertsInformationMap.get(expertsScheduleDaily.getDoctorId());
            HsDepartment dept = expert == null ? null : departmentMap.get(expert.getDepartmentId());
            User user = userMap.get(appointment.getUserId());
            result.add(new CockpitExpertsAppointment()
                    .setId(appointment.getId())
                    .setUserName(user == null ? "" : user.getName())
                    .setCancelTime(appointment.getUpdateTime())
                    .setDept(dept == null ? "" : dept.getName())
                    .setAppointmentDate(appointment.getAppointmentDate())
                    .setDayStr(expertsScheduleDaily.getVisitDay().substring(2))
                    .setDoctorName(expert == null ? "" : expert.getName())
                    .setUserPhone(user == null ? "" : user.getPhone())
                    .setVisitTime(expertsScheduleDaily.getVisitTime()));
        }
        return result;
    }
}
